<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
<html>
<head>
<title>Custom widget in GTK#</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="Custom widget, C#, GTK#, tutorial, programming">
<meta name="description" content="In this part of the GTK# tutorial, we create
a custom widget.">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container2">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content2">


<a href="/" title="Home">Home</a>&nbsp;
<a href="..">Contents</a>


<h1>Custom widget in GTK#</h1>

<p>
Have you ever looked at an application and wondered, how a particular gui item was created?
Probably every wannabe programmer has. Then you were looking at a list of widgets provided 
by your favourite gui library. But you couldn't find it. Toolkits usually provide only the 
most common widgets like buttons, text widgets, sliders etc. No toolkit can provide all 
possible widgets.
</p>

<div class="big_hor">
<script type="text/javascript"><!--
google_ad_client = "ca-pub-9706709751191532";
/* big_horizontal */
google_ad_slot = "2904953388";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<p>
There are actually two kinds of toolkits. Spartan toolkits and heavy weight toolkits. 
The FLTK toolkit is a kind of a spartan toolkit. It provides only the very basic 
widgets and assumes, that the programemer will create the more complicated ones himself. 
wxWidgets is a heavy weight one. It has lots of widgets. Yet it does not provide the more 
specialized widgets. For example a speed meter widget, a widget that measures the capacity 
of a CD to be burned (found e.g. in nero). Toolkits also don't have usually charts.
</p>

<p>
Programmers must create such widgets by themselves. They do it by using the drawing 
tools provided by the toolkit. There are two possibilities. A programmer can modify or 
enhance an existing widget. Or he can create a custom widget from scratch. 
</p>


<h2>Burning widget</h2>

<p>
This is an example of a widget, that we create from scratch. This widget can be 
found in various media burning applications, like Nero Burning ROM.
</p>

<div class="codehead">burning.cs</div>
<pre class="code">
using Gtk;
using Cairo;
using System;
 
class Burning : DrawingArea
{

    string[] num = new string[] { "75", "150", "225", "300", 
        "375", "450", "525", "600", "675" };

    public Burning() : base()
    {
        SetSizeRequest(-1, 30);
    }

    protected override bool OnExposeEvent(Gdk.EventExpose args)
    {
        
        Cairo.Context cr = Gdk.CairoHelper.Create(args.Window);
        cr.LineWidth = 0.8;

        cr.SelectFontFace("Courier 10 Pitch", 
            FontSlant.Normal, FontWeight.Normal);
        cr.SetFontSize(11);

        int width = Allocation.Width;

        SharpApp parent = (SharpApp) GetAncestor (Gtk.Window.GType);        
        int cur_width = parent.CurValue;

        int step = (int) Math.Round(width / 10.0);

        int till = (int) ((width / 750.0) * cur_width);
        int full = (int) ((width / 750.0) * 700);

        if (cur_width >= 700) {
            
            cr.SetSourceRGB(1.0, 1.0, 0.72);
            cr.Rectangle(0, 0, full, 30);
            cr.Clip();
            cr.Paint();
            cr.ResetClip();
            
            cr.SetSourceRGB(1.0, 0.68, 0.68);
            cr.Rectangle(full, 0, till-full, 30);    
            cr.Clip();
            cr.Paint();
            cr.ResetClip();

        } else { 
             
            cr.SetSourceRGB(1.0, 1.0, 0.72);
            cr.Rectangle(0, 0, till, 30);
            cr.Clip();
            cr.Paint();
            cr.ResetClip();
       }  

       cr.SetSourceRGB(0.35, 0.31, 0.24);
       
       for (int i=1; i<=num.Length; i++) {
           
           cr.MoveTo(i*step, 0);
           cr.LineTo(i*step, 5);    
           cr.Stroke();
           
           TextExtents extents = cr.TextExtents(num[i-1]);
           cr.MoveTo(i*step-extents.Width/2, 15);
           cr.TextPath(num[i-1]);
           cr.Stroke();
       }
        
        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();

        return true;
    }
}


class SharpApp : Window {
 
    int cur_value = 0;
    Burning burning;
    
    public SharpApp() : base("Burning")
    {
        SetDefaultSize(350, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };
       
        VBox vbox = new VBox(false, 2);
        
        HScale scale = new HScale(0, 750, 1);
        scale.SetSizeRequest(160, 35);
        scale.ValueChanged += OnChanged;
        
        Fixed fix = new Fixed();
        fix.Put(scale, 50, 50);
        
        vbox.PackStart(fix);
        
        burning = new Burning();
        vbox.PackStart(burning, false, false, 0);

        Add(vbox);

        ShowAll();
    }
    
    void OnChanged(object sender, EventArgs args)
    {
        Scale scale = (Scale) sender;
        cur_value = (int) scale.Value;
        burning.QueueDraw();
    }
    
    public int CurValue {
        get { return cur_value; }
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}
</pre>

<p>
We put a <code>DrawingArea</code> on the bottom of the window and draw 
the entire widget manually. All the important code resides in the 
<code>OnExposeEvent()</code> method of the Burning class. This widget shows 
graphically the total capacity of a medium and the free space available to us. 
The widget is controlled by a scale widget. The minimum value of our custom 
widget is 0, the maximum is 750. If we reach value 700, we began drawing in 
red colour. This normally indicates overburning.  
</p>

<pre class="explanation">
string[] num = new string[] { "75", "150", "225", "300", 
    "375", "450", "525", "600", "675" };
</pre>

<p>
These numbers are shown on the burning widget. They show the capacity of the medium.
</p>


<pre class="explanation">
SharpApp parent = (SharpApp) GetAncestor (Gtk.Window.GType);        
int cur_width = parent.CurValue;
</pre>

<p>
These two lines get the current number from the scale widget. We get the parent widget and 
from the parent widget, we get the current value. 
</p>

<pre class="explanation">
int till = (int) ((width / 750.0) * cur_width);
int full = (int) ((width / 750.0) * 700);
</pre>

<p>
The till parameter determines the total size to be drawn. This value comes from the slider widget. 
It is a proportion of the whole area. The full parameter determines the point,
where we begin to draw in red color. 
</p>


<pre class="explanation">
cr.SetSourceRGB(1.0, 1.0, 0.72);
cr.Rectangle(0, 0, full, 30);
cr.Clip();
cr.Paint();
cr.ResetClip();
</pre>

<p>
This code here, draws a yellow rectangle up to point, where the medium is full.
</p>

<pre class="explanation">
TextExtents extents = cr.TextExtents(num[i-1]);
cr.MoveTo(i*step-extents.Width/2, 15);
cr.TextPath(num[i-1]);
cr.Stroke();
</pre>

<p>
This code here draws the numbers on the burning widget. We calculate the
<code>TextExtents</code> to position the text correctly.
</p>

<pre class="explanation">
void OnChanged(object sender, EventArgs args)
{
    Scale scale = (Scale) sender;
    cur_value = (int) scale.Value;
    burning.QueueDraw();
}
</pre>

<p>
We get the value from the scale widget, store it in the <code>cur_value</code> variable 
for later use. We redraw the burning widget. 
</p>

<img src="/img/gui/sharpgtk/burning.png" alt="Burning widget">
<div class="figure">Figure: Burning widget</div>

<p>
In this chapter, we created a custom widget in GTK#.
</p>

<div class="center">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>
<br>


<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified January 21, 2009  <span class="copyright">&copy; 2007 - 2013 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>

